#include "minicon.h"
#include "miniopt.h"
#include <sddl.h>

extern int err_ret(int errcode, const char *msg, const WCHAR *name);

typedef struct _SYSTEM_TIME_OF_DAY_INFORMATION 
{
   LARGE_INTEGER BootTime;
   LARGE_INTEGER CurrentTime;
   LARGE_INTEGER TimeZoneBias;
   ULONG CurrentTimeZoneId;
} SYSTEM_TIME_OF_DAY_INFORMATION, *PSYSTEM_TIME_OF_DAY_INFORMATION;

#define SystemTimeOfDayInformation 3

//--------------------------------------------------------------------------
#if defined(_WIN64) || defined(_DEBUG) || !defined(_M_IX86)

static DWORD __forceinline lldiv( __int64 &dividend, DWORD divisor )
{
   DWORD reminder = (DWORD)(dividend % divisor);
   dividend /= divisor;
   return reminder;
}

#else

__declspec(naked)
DWORD __fastcall lldiv( __int64 &dividend, DWORD divisor )
{
   // lea   ecx, dividend
   // mov   edx, divisor
   // call  lldiv
   #define divd_LO_  dword ptr [ecx]
   #define divd_HI_  dword ptr [ecx+4]
   #define divisor_  edx
   __asm {
      push  ebx
      mov   ebx, divisor_
      mov   eax, divd_HI_
      xor   edx, edx
      div   ebx
      mov   divd_HI_, eax
      mov   eax, divd_LO_
      div   ebx
      mov   divd_LO_, eax
      xchg  edx, eax
      pop   ebx
      ret
   }
   (void)dividend, divisor;
}

#endif
//--------------------------------------------------------------------------

#pragma warning(disable : 4701) // potentially not initialized variable 'st'
static int enum_users( int verbose )
{
   WCHAR nm[128], domain[128], user[128], sess[64], cname[64], sessn[32];
   int nusers = 0;
   DWORD item = 0, nc = (DWORD)dim(nm);
   FILETIME login_time = {0, 0}, lt;

   while ( ERROR_SUCCESS == ::RegEnumKeyExW(HKEY_USERS, item,nm,&nc, NULL,NULL,NULL,NULL) )
   {
      HKEY hk, hk1, hk2;
      if ( ERROR_SUCCESS == ::RegOpenKeyExW(HKEY_USERS, nm, 0, KEY_READ, &hk) )
      {
         if ( ERROR_SUCCESS == ::RegOpenKeyExW(hk, L"Volatile Environment", 0, KEY_READ, &hk1) )
         {
            ++nusers;
            if ( verbose > 0 )
            {
               domain[0] = user[0] = sess[0] = cname[0] = sessn[0] = L'\0';

               ::RegQueryInfoKeyW(hk1, NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, &login_time);

               PSID psid = NULL;
               union {
                  FARPROC f;
                  BOOL (WINAPI *ConvertStringSidToSidW)(IN LPCWSTR StringSid, OUT PSID *Sid);
               } u;
               u.f = ::GetProcAddress(Win::hadv, "ConvertStringSidToSidW");

               if ( u.f && u.ConvertStringSidToSidW(nm, &psid) )
               {
                  DWORD ncd = 128, ncu = 128; SID_NAME_USE nm_use;
                  ::LookupAccountSidW(NULL, psid, user,&ncu, domain,&ncd, &nm_use);
                  ::LocalFree(psid);
                  psid = NULL;
               }

               nc = sizeof(sess);
               if (ERROR_SUCCESS != ::RegQueryValueExW(hk1, L"SESSIONNAME", NULL,NULL,(LPBYTE)sess,&nc))
               {
                  nc = (DWORD)dim(sessn);
                  // TODO: only one session processed here
                  if ( ERROR_SUCCESS == ::RegEnumKeyExW(hk1, 0,sessn,&nc, NULL,NULL,NULL,NULL) )
                  {
                     if ( ERROR_SUCCESS == ::RegOpenKeyExW(hk1, sessn, 0, KEY_READ, &hk2) )
                     {
                        nc = sizeof(sess);
                        ::RegQueryValueExW(hk2, L"SESSIONNAME", NULL,NULL,(LPBYTE)sess,&nc);
                     }
                  }
               }

               if ( !user[0] ) ::lstrcpyW(user, L"unknown");
               SYSTEMTIME st;
               st.wYear = 0;
               if ( ::FileTimeToLocalFileTime(&login_time, &lt) ) ::FileTimeToSystemTime(&lt, &st);

               if ( verbose < 2 )
               {
                  Con::f(L"%-15s %-15s ", user, sess[0] ? sess : L"unknown");
                  if (0 == st.wYear) Con::o("unknown\r\n"); else
                  Con::f("%hu-%02hu-%02hu %02hu:%02hu:%02hu\r\n",
                     st.wYear,st.wMonth,st.wDay, st.wHour,st.wMinute,st.wSecond
                  );
               }
               else
               {
                  nc = sizeof(cname);
                  ::RegQueryValueExW(sessn[0] ? hk2 : hk1, L"CLIENTNAME", NULL,NULL,(LPBYTE)cname,&nc);
                  HKEY hk3;
                  DWORD pc = 0;
                  if (ERROR_SUCCESS == ::RegOpenKeyExA(hk,"SessionInformation",0,KEY_READ,&hk3))
                  {
                     nc = sizeof(pc);
                     ::RegQueryValueExW(hk3, L"ProgramCount", NULL,NULL, (LPBYTE)&pc, &nc);
                     ::RegCloseKey(hk3);
                  }

                  Con::f(L"%s {\r\n", user);
                  if ( domain[0] ) Con::f(L"  Domain name  : %s\r\n", domain);
                  if ( sess  [0] ) Con::f(L"  Session name : %s\r\n", sess  );
                  if ( cname [0] ) Con::f(L"  Client name  : %s\r\n", cname );
                  if (st.wYear)    Con::f( "  Login time   : %hu-%02hu-%02hu %02hu:%02hu:%02hu\r\n",
                     st.wYear,st.wMonth,st.wDay, st.wHour,st.wMinute,st.wSecond
                  );
                  if (pc > 0) Con::f( "  ProgramCount : %u\r\n", pc);
                  Con::f("}\r\n");
               }
            }

            if (sessn[0]) ::RegCloseKey(hk2);
            ::RegCloseKey(hk1);
         }
         ::RegCloseKey(hk);
      }
      ++item; nc = (DWORD)dim(nm);
   }
   return nusers;
}
#pragma warning(default : 4701)

//--------------------------------------------------------------------------

int who( bool verbose )
{
   return enum_users( verbose ? 2 : 1 );
}

//--------------------------------------------------------------------------

int uptime( bool boottime )
{
   SYSTEM_TIME_OF_DAY_INFORMATION ti;
   union {
      FARPROC f;
      LONG (WINAPI *NtQuerySystemInformation)(UINT,PVOID,ULONG,PULONG);
   } u;
   u.f = ::GetProcAddress(Win::hnt, "NtQuerySystemInformation");

   if (!u.f || 0 != u.NtQuerySystemInformation(SystemTimeOfDayInformation,&ti,sizeof(ti),0))
   {
      return err_ret(-21, "Can't retrieve system boot time", NULL);
   }

   FILETIME lft; SYSTEMTIME st;
   union { FILETIME ft; LARGE_INTEGER li; __int64 ll; } bt, ct;
   bt.li = ti.BootTime;
   ct.li = ti.CurrentTime;

   if ( boottime )
   {
      if (!::FileTimeToLocalFileTime(&bt.ft, &lft) || !::FileTimeToSystemTime(&lft, &st))
      {
         return err_ret(-22, "Bad boot time retrieved", NULL);
      }
      Con::f(" system boot %hu-%02hu-%02hu %02hu:%02hu:%02hu\r\n",
         st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond
      );
      return 0;
   }

   if (!::FileTimeToLocalFileTime(&ct.ft, &lft) || !::FileTimeToSystemTime(&lft, &st))
   {
      return err_ret(-23, "Bad current time retrieved", NULL);
   }
   Con::f(" %02hu:%02hu:%02hu  up ", st.wHour, st.wMinute, st.wSecond);

   __int64 diff = ct.ll - bt.ll;
   lldiv(diff, 10*1000*1000);
   DWORD ut = (DWORD)diff;
   if ( ut >= 24*3600 ) { Con::f("%u days, ", ut/(24*3600)); ut %= 24*3600; }
   Con::f("%u:%02u:%02u,  %d user\r\n",
      ut / 3600, ut / 60 % 60, ut % 60, enum_users(0)
   );

   return 0;
}
